// Haxzs Audio Server
// made by omghaxzs
// 2010

#include "client.h"
#include "http.h"
#include "log.h"
#include "dest.h"
#include "clientwnd.h"
#include "sourcewnd.h"
#include "net.h"
#include "settings.h"
#include "source.h"

using namespace std;
using boost::asio::ip::tcp;

client::client() : clientsocket(io_service)
{
	newdata = false;
	cachefull = false;
	errorcode = 0;
	bytes = 0;
	int buf = atoi(setting->getSetting("buffer_length").c_str());
	buffer_len = &buf;
}

void client::start()
{
	// Initialize our thread
	m_Thread = boost::thread(&client::communicate, this);

	if (clientType == CLIENT_TYPE_SOURCE)
	{
		m_Thread.join();
	}
}

void client::communicate()
{
	// Is the client connecting a source?
	if (clientType == CLIENT_TYPE_SOURCE)
	{
		// Send it a message asking for info about the source
		this->sendMsg("ident");

		// Recieve the identifier
		std::string buf;
		buf = this->recvMsg();
		ident = buf;

		// Get the format
		this->sendMsg("format");
		buf = this->recvMsg();
		format = buf;

		// Get the bitrate
		this->sendMsg("rate");
		buf = this->recvMsg();
		rate = buf;

		// The source has officially connected!
		srcwnd->sourceConnected(true);
		srcwnd->updateSourceIP(ip);
		srcwnd->updateSourceInfo(ident, format, rate);

		// Alright, now that we have the info, tell it to start sending data
		this->sendMsg("start");
	}
	else
	{
		// Is the source connected?
		if (!s->connected)
		{
			// We don't care
			this->disconnect();
		}
		// No it's not.
		// What did it send?
		std::string buf;
		buf = this->recvMsg();

		// Try to process it if it's HTTP data
		http *h = new http();
		if (!h->ParseHTTP(buf))
		{
			// Malformed HTTP data, disconnecting
			this->disconnect();
		}
		else if (buf == "status")
		{
			// Send how many listeners are currently on
			std::stringstream Num;
			Num << d->clients.size();
			this->sendMsg(Num.str());

			// Disconnect
			this->disconnect();
		}
		else
		{
			// Nope, not malformed
			// Let's just send them a response with the MP3 data back for now
			h = new http();

			h->type = MESSAGE_TYPE::MESSAGE_RESPONSE;
			h->resp.code = "200 OK";
			h->resp.version = HTTP_VERSION::HTTP_VERSION_1_1;

			HTTP_HEADER new_header;
			new_header.header = "Content-Type";
			new_header.body = "audio/mpeg";
			h->headers.push_back(new_header);

			new_header.header = "icy-br";
			new_header.body = "64";
			h->headers.push_back(new_header);

			new_header.header = "icy-pub";
			new_header.body = "0";
			h->headers.push_back(new_header);

			//new_header.header = "icy-metaint";
			//new_header.body = "8192";
			//h->headers.push_back(new_header);

			new_header.header = "icy-name";
			new_header.body = "Underground Sound";
			h->headers.push_back(new_header);

			std::string output;
			output = h->Output();
			output += "\r\n";

			this->sendMsg(output);

			// Now we send the source data
			this->sendSourceData();
		}
	}
}

void client::sendSourceData()
{
	int bufferlen = 0, sentbytes = 0;

	// Loop until there is no more source
	while(this->errorcode == 0)
	{
		try
		{
			// Is it okay to send?
			if (this->okaytosend)
			{
				// Yep, let's start sending
				bufferlen = 0;
				sentbytes = 0;

				// Is the cache alright?
				if (!s->connected)
				{
					// Nope
					break;
				}

				// Check if the source has new data
				// Go through all the clients and send them the data

				try
				{
					// Grab just a part of the cache
					part = this->cache.substr(0, DEFAULT_BUFLEN);
				}
				catch(std::exception& e)
				{
					return;
				}

				// Is the part as big as DEFAULT_BUFLEN?
				if (part.size() >= DEFAULT_BUFLEN)
				{
					// Yes, we will sent that much
					bufferlen = DEFAULT_BUFLEN;
				}
				else
				{
					// No, the buffer size is less than that
					bufferlen = part.size();
				}

				try
				{
					// Prepare to send!
					boost::system::error_code ignored_error;
					sentbytes += boost::asio::write(clientsocket, boost::asio::buffer(part), boost::asio::transfer_all(), ignored_error); // Sent

					if (sentbytes > 0)
					{
						// It sent perfectly, erase the cache that we used
						this->cache.erase(0, part.size());
					}
					else if (ignored_error)
					{
						// Uh oh, an error occured
						this->errorcode = ignored_error.value();
					}
				}
				catch(std::exception& e)
				{
					return;
				}
			}

			// Sleep a little
			boost::this_thread::sleep(boost::posix_time::milliseconds(10));
		}
		catch(std::exception& e)
		{
			// An error occured!
			// Is the source connected?
			if (s->connected)
			{
				// Dunno what happened, explain the error
				l->printMsg(e.what(), true);
			}
			else
			{
				// Yeah, the source is disconnected.
				// Sleep for a few
				boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
			}
		}
	}
}

void client::sendMsg(std::string str)
{
	int iSendResult = 0;

	try
	{
		// Send the message
		boost::system::error_code error;
		iSendResult += boost::asio::write(clientsocket, boost::asio::buffer(str), boost::asio::transfer_all(), error);

		if (error)
		{
			this->errorcode = error.value();
			return;
		}
	}
	catch(std::exception& e)
	{
		l->printMsg(e.what(), true);
	}
}

void client::sendData(char data[])
{
	int iSendResult;

	try
	{
		std::string buf;
		buf.assign(data);

		boost::system::error_code error;
		iSendResult = boost::asio::write(clientsocket, boost::asio::buffer(buf, buf.size()), boost::asio::transfer_all(), error);

		if (error)
		{
			this->errorcode = error.value();
		}
	}
	catch(std::exception& e)
	{
		l->printMsg(e.what(), true);
	}

}

std::string client::recvData()
{
	int iResult;
	int bytesneeded = 0;

	char buf[DEFAULT_BUFLEN];
	boost::system::error_code error;

	try
	{
		// Receive the message
		iResult = boost::asio::read(clientsocket, boost::asio::buffer(buf, DEFAULT_BUFLEN), boost::asio::transfer_all(), error);

		// Was anything recieved?
		if (iResult > 0)
		{
			// Yep
			newdata = true;

			recvbuf.assign(buf, iResult);

			return recvbuf;
		}
		else if (error)
		{
			// No, there was an error

			// Create a string
			std::string str = "";

			str += "Client encountered an error.";

			this->errorcode = error.value();

			// Copy the first part of the message
			l->printMsg(str, true);
		}
	}
	catch(std::exception& e)
	{
		// Just incase!
		l->printMsg(e.what(), true);
	}

	// Unallocate the memory
	return recvbuf;
}

std::string client::recvMsg()
{
	int iResult;
	int bytesneed = 0;

	boost::array<char, DEFAULT_BUFLEN> buf;
	boost::system::error_code error;

	try
	{
		// Receive the message
		iResult = boost::asio::read(clientsocket, boost::asio::buffer(buf), boost::asio::transfer_at_least(3), error);

		if (iResult > 0)
		{
			recvbuf.clear();
			recvbuf.reserve(iResult);
			recvbuf.assign(buf.data(), 0, iResult);

			newdata = true;

			return recvbuf;
		}
		else if (error)
		{
			// Create a string
			std::string str = "";

			str += "Client encountered an error.";

			this->errorcode = error.value();

			// Copy the first part of the message
			l->printMsg(str, true);
		}
	}
	catch(std::exception& e)
	{
		l->printMsg(e.what(), true);
	}

	// Unallocate the memory
	return recvbuf;
}

void client::disconnect()
{
	try
	{
		clientsocket.shutdown(boost::asio::socket_base::shutdown_send);
		clientsocket.close();

		this->errorcode = 1;
		this->connected = false;
	}
	catch(std::exception& e)
	{
		l->printMsg(e.what(), true);
	}
}

void client::processCache(std::string data)
{
	try
	{
		if (cache.size() > DEFAULT_BUFLEN * 2)
		{
			if (clientType == CLIENT_TYPE_SOURCE)
			{
				cache.erase(0, data.size());
			}
		}
		cache.append(data);
		if (clientType == CLIENT_TYPE_SOURCE)
		{
			std::stringstream Num;
			std::string str;

			// Calculate the percent
			float per = 0;

			if (cache.size() != 0)
			{
				per = (cache.size() / (DEFAULT_BUFLEN * 2.0f)) * 100.0f;
				Num << per;
				str = Num.str();
				srcwnd->updateSourceCache(str);
			}
		}
	}
	catch(std::exception& e)
	{
		l->printMsg(e.what(), true);
	}
}